Nested Lists

We've seen that we can have lists where the elements are different types. If the elements of a list are themselves type list, then we call this a nested list. Here is an example.

In [15]:
rhymes = [['cat', 'hat', 'fat'], ['mouse', 'house', 'louse'], ['be', 'see', 'key', 'he']]

What is the type of rhymes?

In [16]:
type(rhymes)
Out[16]:
list
In [17]:
type(rhymes[0])
Out[17]:
list
In [18]:
rhymes[0]
Out[18]:
['cat', 'hat', 'fat']
In [19]:
rhymes[0][1]
Out[19]:
'hat'

What expression would give us 'louse'?

'key'?

the list ['be', 'see', 'key', 'he']?

the list ['hat', 'fat']?

Looping over a nested List

Consider the following loop using our rhymes list.

In [20]:
for element in rhymes:
    # do something with element
    print(element)
['cat', 'hat', 'fat']
['mouse', 'house', 'louse']
['be', 'see', 'key', 'he']

Each time through the loop element is one of the inner lists. Let's use this to solve a problem.

An example

Suppose we have a list where each element represents the visit dates for a particular patient in the last month. We want to calculate the highest number of visits made by any patient. Let's write a function to do this.

First an example call:

>>> max_visits([[2, 6], [3, 10], [15], [23], [1, 8, 15, 22, 29], [14]])

5

Next the type contract (list of list of int) -> int

Next the header (the def line) which means naming the parameter

In [21]:
def max_visits(visits_by_patient):
    ''' (list of list of it) -> int 
    
    Return the maximum number of visits made by any paitent
    >>> max_visits([[2, 6], [3, 10], [15], [23], [1, 8, 15, 22, 29], [14]])
    5
    '''
Now code the body.

If we had a single list of visit dates, and wanted to know the number of visits what would we do? Call len on the list. So, we need to do this for each element of our visits_by_patient outer list.

In [22]:
def max_visits(visits_by_patient):
    ''' (list of list of it) -> int 
    
    Return the maximum number of visits made by any paitent
    >>> max_visits([[2, 6], [3, 10], [15], [23], [1, 8, 15, 22, 29], [14]])
    5
    '''
    max_so_far = 0
    for patient_list in visits_by_patient:
        visits = len(patient_list)
        if (visits) > max_so_far:
            max_so_far = visits
    return max_so_far
In [23]:
max_visits([[2, 6], [3, 10], [15], [23], [1, 8, 15, 22, 29], [14]])
Out[23]:
5

Exercise

Suppose we have a nested list where each inner list contains strings that represent symptoms exhibited by the corresponding patient. We want to write a function that takes this list as a parameter and returns a new list containing integers. For each patient, the new list should contain the number of symptoms they were exhibiting.

Here is an example:

>>> symptom_count(['fatique', 'abdominal swelling', 'bruising'], ['loss of appetite', 'fatique'])

[3, 2]

Follow the design recipe and start by writing the docstring.

Solution

In [24]:
def symptom_count(symptoms):
    """ (list of list of str) -> (list of it)
    
    Return a list of the number of symptoms for each patient in symptoms.
    
    >>> symptom_count(['fatique', 'abdominal swelling', 'bruising'], ['loss of appetite', 'fatique'])
    [3, 2]
    
    """
    result = []
    for symptom_list in symptoms:
        result.append(len(symptom_list))
    return result

Heterogeneous Lists

In Python, all the elements of a list don't have to be the same type. That means it is possible to have a list like this:

['Milos', 'Jones', 48, 'male', 'smoker', 210]

which represents one person giving personal information. (The last number is total cholesterol in mg/dL.)

Then we could have a list of elements like this to represent a list of people.

[ ['Milos', 'Jones', 48, 'male', 'smoker', 210], ['Delia', 'Chan', 39, 'female', 'non-smoker', 170], ['Denise', 'Ross', 62, 'female', 'non-smoker' 150] ]

Suppose we have such a list in the variable patients, Write code that would loop over this list and create a list of the last names of only the female patients.

Solution

In [25]:
patients = [ ['Milos', 'Jones', 48, 'male', 'smoker', 210], 
             ['Delia', 'Chan', 39, 'female', 'non-smoker', 170], 
             ['Denise', 'Ross', 62, 'female', 'non-smoker', 150] ]

female_patients = []
for solo_patient in patients:
    if solo_patient[3] == 'female':
        female_patients.append(solo_patient[1])

print(female_patients)
['Chan', 'Ross']

Homework: write more interesting function where need to access 2 elements to make decision and need to make lastname, firstname combo to put in created list.

Nested Loops

Sometimes we might want to loop over the inner list and then do this inside loop at each inner list -- so loop over the outer list.

Suppose our list represents repeated heart-rate measurements all for the same patient over a number of tests. Each inner-list is a test/situation and for that test, we monitored the heart rate for a little while taking a few measurements. Now we would like to calculate the average of the measurements for each test.

hr = [ [ 72, 75, 71, 73], # resting

[ 91, 90, 94, 93], # walking slowly

[ 130, 135, 139, 142], # running on treadmill

[ 120, 118, 110, 105, 100, 98]] # after minute recovery

Suppose we can't use sum() function on a list since the point is to use a loop inside outer loop!

Solution

In [26]:
hr = [ [ 72, 75, 71, 73],      # resting
[ 91, 90, 94, 93],             # walking slowly
[ 130, 135, 139, 142],         # running on treadmill
[ 120, 118, 110, 105, 100, 98] # after a minute recovery
]

# start with an empty list that we will build to return (or print)
result = []

# loop over the outer list, each element is a test
for test in hr:
    
    # reset the sum for this test to 0
    sum = 0
    
    # loop over the inner list
    for measurement in test:
        sum = sum + measurement
        
    # finish up with this test before repeating the loop for the next one    
    average = sum / len(test)
    result.append(average)
    
print(result)
[72.75, 92.0, 136.5, 108.5]

Exercise

Find range of heart rate difference for each inner list. So for example above, it would be [4, 4, 12, 22]